<?php
/**
 * <pre>
 * <b>RUBIK IT Solution & Development ltd.</b>
 *
 * PROJECT      : rubik-base
 * COPYRIGHT    : 2008
 * ------------------------------------------------------
 * 
 * [GENERAL INFO OF THIS FILE] 
 *
 * </pre>
 * 
 * @version 		1.5
 * @author			Dang Nhon Hoa <HoaDN@rubik.com.vn>
 * @access			public
 * @package			rubik.base.dbo
 *
 * Created on	: May 5, 2008
 */
RCoreImporter::cls("dbo.RubikDBField" );
RCoreImporter::cls("dbo.RubikDBFieldList" );

define ( "SUBFIX_KEY", "_id" );

/**
 * The abstract class of RUBIK for management the Database Object
 *
 */
class RubikDBInfo {
	/**
	 * All boolean fields list of current DB
	 *
	 * @var RubikDBFieldList
	 */
	var $_booleanList = null;
	
	/**
	 * All numericList fields list of current DB
	 *
	 * @var RubikDBFieldList
	 */
	var $_numericList = null;
	
	/**
	 * All string fields list of current DB
	 * 
	 * @var RubikDBFieldList
	 */
	var $_stringList = null;
	
	/**
	 * All date fields list of current DB
	 * 
	 * @var RubikDBFieldList
	 */
	var $_dateList = null;
	
	/**
	 * All fields list of current DB
	 * 
	 * @var RubikDBFieldList
	 */
	var $_allFields = null;
	
	/////////////////////////////////////////////////////////////////////////////////////////////////////////////
	

	/**
	 * The from clause.
	 * Ex: " FROM user u JOIN group g ON u.group_id = g.group_id"
	 *
	 * @var string
	 */
	var $_from = null;
	
	/**
	 * Table name of this DB
	 * This field is optional
	 *
	 * @var string
	 */
	var $_tbl_name = null;
	
	/**
	 * Key name of the table
	 * shouldn't use this derectly
	 *
	 * @var string
	 * @deprecated shouldn't use this derectly
	 */
	var $_key_name = null;
	
	/**
	 * The database object of this DB info
	 *
	 * @var Database
	 */
	var $_db = null;
	
	//////////////////////////////////////////////////////////////////////////////////////////////////////////
	

	/**
	 * Select clause
	 *
	 * @var string
	 */
	var $_select = null;
	
	/**
	 * Constructor
	 *
	 * @param AbstractRubikDBObject $rubikDBO DB object for getting infromation
	 * @param string $from this can be a from clause of just table name.
	 * @param Database $db database object of this DB Info.
	 */
	function RubikDBInfo($rubikDBO, $from = '', $db = null) {
		$this->_db = $db;
		$this->_createInfo ( $rubikDBO );
		$this->setFromClause ( $from );
	}
	
	/**
	 * Get from clause of this
	 *
	 * @return string
	 */
	function getFromClause() {
		return $this->_from;
	}
	
	/**
	 * Set from value
	 *
	 * @param string $from this can be a from clause of just table name.
	 */
	function setFromClause($from) {
		if (empty ( $from )) {
			return;
		}
		
		$lowerFrom = strtoupper ( $from );
		if (strpos ( $lowerFrom, "FROM" ) === false) {
			$this->_tbl_name = $from;
			$this->_from = " FROM " . $this->dbQuoteField ( $from );
		} else {
			$this->_from = $from;
		}
	}
	
	/**
	 * Get table_name clause of this
	 *
	 * @return string
	 */
	function getTableName() {
		return $this->_tbl_name;
	}
	
	/**
	 * Set the table name and key name
	 *
	 * @access protected
	 * @param string $tableName
	 * @param string $keyName
	 */
	function _setTableKey($tableName, $keyName) {
		$this->_setKeyName ( $keyName );
		$this->_setTableName ( $tableName );
	}
	
	/**
	 * Get the key name of table if exists
	 *
	 * @return string
	 */
	function getKeyName() {
		if (empty ( $this->_key_name )) {
			if (empty ( $this->_tbl_name )) {
				return null;
			} else {
				$this->_key_name = $this->_tbl_name . SUBFIX_KEY;
			}
		}
		
		return $this->_key_name;
	}
	
	/**
	 * Set the key name for the table
	 *
	 * @param string $keyName
	 * @access protected
	 */
	function _setKeyName($keyName) {
		$allFields = $this->_allFields;
		$dbField = $allFields->getDBField ( $keyName );
		$dbField->isKey = true;
		$this->_key_name = $keyName;
	}
	
	/**
	 * Set the table name
	 *
	 * @param string $tableName
	 * @access protected
	 */
	function _setTableName($tableName) {
		$this->_tbl_name = $tableName;
	}
	
	/**
	 * Create the information form a DB object
	 * 
	 * @param AbstractRubikDBObject $dbo the dbo reference object
	 */
	function _createInfo($dbo) {
		$booleanList = &$this->_booleanList;
		$numericList = &$this->_numericList;
		$stringList = &$this->_stringList;
		$dateList = &$this->_dateList;
		
		$allFields = &$this->_allFields;
		
		if (! empty ( $stringList )) {
			// in case we calculated --> to reduce the performance --> no more calculate.
			return;
		}
		
		//////////////////////////////////////////////////////////////////////////////////////////////
		// This is the first time this class has been created --> calculate information for it
		

		// init the array
		$booleanList = new RubikDBFieldList ( );
		$numericList = new RubikDBFieldList ( );
		$stringList = new RubikDBFieldList ( );
		$dateList = new RubikDBFieldList ( );
		
		// Begin to crete
		$vars = get_object_vars ( $dbo );
		
		foreach ( $vars as $key => $value ) {
			if ('_' == substr ( $key, 0, 1 )) {
				continue;
			}
			
			//this is the public properties
			$dbField = new RubikDBField ( $key, $value );
			if ($dbField->isBoolean ()) {
				$booleanList->addDBField ( $dbField );
			} else if ($dbField->isDate ()) {
				$dateList->addDBField ( $dbField );
			} else if ($dbField->isNumeric ()) {
				$numericList->addDBField ( $dbField );
			} else {
				$stringList->addDBField ( $dbField );
			}
			
			// TODO for future version may be this isn't just 1 field
			if ($dbField->isKey) {
				$this->_key_name = $key;
			}
		}
		
		$allFields = new RubikDBFieldList ( );
		$allFields->addFields ( $booleanList );
		$allFields->addFields ( $numericList );
		$allFields->addFields ( $stringList );
		$allFields->addFields ( $dateList );
	}
	
	/**
	 * Get the select SQL clause
	 *
	 * @param string $extKey the extend key like: "DISTINCT", "TOP 1", ....
	 * @return string
	 */
	function getSelectSQL($extKey = '') {
		$selectClause = $this->getSelectClause ( $extKey );
		$selectClause .= "\n" . $this->_from;
		return $selectClause;
	}
	
	/**
	 * Set select clause for this DBInfo
	 * WARNING: be carefull when using this function
	 *
	 * @param string $extKey the extend key like: "DISTINCT", "TOP 1", ....
	 * @return string $select
	 */
	function getSelectClause($extKey = '') {
		if (! empty ( $this->_select )) {
			return $this->_select;
		}
		
		$selectClause = "SELECT {$extKey} ";
		
		$allFields = &$this->_allFields;
		$selectClause .= $allFields->getSelectString ( $this );
		
		$selectClause = substr ( $selectClause, 0, strlen ( $selectClause ) - 2 );
		$this->_select = $selectClause;
		
		return $this->_select;
	}
	/**
	 * Set select clause for this DBInfo
	 * WARNING: be carefull when using this function
	 *
	 * @param string $select
	 * @deprecated do not use this function if you don't really know about this class
	 */
	function setSelectClause($select) {
		$this->_select = $select;
	}
	
	/**
	 * Set default value for RubikDB object
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 */
	function setDefaultValue(&$rubikDBO) {
		$this->_allFields->setDefaultValueForDB ( $rubikDBO );
	}
	
	/**
	 * Set all empty number fields to Zero number
	 * Using this in case of update some value that data is null
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 */
	function setEmptyNumberToZero(&$rubikDBO) {
		$this->_numericList->setEmptyNumberToZero($rubikDBO);
	}
	
	/**
	 * Set all empty boolean fields to Zero number
	 * Using this in case of update some value that data is null
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 */
	function setEmptyBooleanToZero(&$rubikDBO) {
		$this->_booleanList->setEmptyNumberToZero($rubikDBO);
	}
	
	/**
	 * Set all value into Null
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 */
	function setNullAllValues(&$rubikDBO) {
		$this->_allFields->setNullAllValues ( $rubikDBO );
	}
	
	/**
	 * Set all empty field into null value
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 */
	function setEmptyToNull(&$rubikDBO) {
		$this->_allFields->setEmptyToNull ( $rubikDBO );
	}
	
	/**
	 * Preparing data before saving it into database.
	 * This step help do some generic and reduce problem of SQL injection
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 */
	function prepareData(&$rubikDBO) {
		$this->_allFields->prepareDataForDB ( $rubikDBO );
	}
	
	/**
	 * Preparing data before using it for viewing.
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 */
	function prepareViewData(&$rubikDBO) {
		$this->_allFields->prepareDataForView ( $rubikDBO );
	}
	
	/**
	 * Check data for mandatory fields
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 * @return true/array of error fields
	 */
	function checkMandatory($rubikDBO) {
		return $this->_allFields->checkMandatory ( $rubikDBO );
	}
	
	/**
	 * Check data for mandatory fields
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 * @return true/array of error fields
	 */
	function checkUnique($rubikDBO) {
		return $this->_allFields->checkUnique ( $rubikDBO, $this );
	}
	
	/**
	 * Check if value of $checkField in $rubikDBO is existing in database or not
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 * @param string/array $checkFieldOrArrayFields
	 * @param boolean $fillBack should fill back data if exist?
	 * @return boolean
	 */
	function checkExisting($rubikDBO, $checkFieldOrArrayFields, $fillBack = false) {
		return $this->_allFields->checkExisting ( $checkFieldOrArrayFields, $rubikDBO, $this, null, $fillBack );
	}
	
	/**
	 * Create the where to check Existing.
	 *
	 * @param string/array $fieldOrArrayFields
	 * @param AbstractRubikDBObject $rubikDBO
	 * @return string
	 */
	function createWhereCheckExist($rubikDBO, $fieldOrArrayFields) {
		return $this->_allFields->createWhereCheckExist ( $fieldOrArrayFields, $rubikDBO, $this );
	}
	
	/**
	 * Create the update SQL for input RubikDB
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 * @param boolean $ignoreNull update with null values or not
	 * @return string SQL string
	 */
	function createUpdateSQL(&$rubikDBO, $ignoreNull = true, $where = '') {
		if (empty ( $this->_tbl_name )) {
			return false;
		}
		
		$updateStr = $this->_allFields->createUpdateSQL ( $rubikDBO, $ignoreNull );
		if ($updateStr === false) {
			return false;
		}
		if (empty ( $where )) {
			$where = $rubikDBO->getWhereClause ();
			if (! empty ( $where )) {
				$where .= " AND ";
			} else {
				$where = "WHERE ";
			}
			$key = $this->getKeyName ();
			$val = $rubikDBO->$key;
			$key = $this->dbQuoteField ( $key );
			if ($val === null) {
				$where .= "$key = NULL";
			} else {
				$val = $this->dbQuoteData ( $val );
				$where .= "$key = $val";
			}
		}
		$tblName = $this->dbQuoteField ( $this->_tbl_name );
		$sql = "UPDATE $tblName";
		$sql .= "\n SET " . $updateStr;
		$sql .= "\n $where;";
		return $sql;
	}
	
	/**
	 * Create the insert SQL for input RubikDB
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 * @param boolean $ignoreNull update with null values or not
	 * @return string SQL string
	 */
	function createInsertSQL(&$rubikDBO, $ignoreNull = true) {
		if (empty ( $this->_tbl_name )) {
			return false;
		}
		
		$fieldList = $this->getFieldList ( $rubikDBO, $ignoreNull );
		
		$size = count ( $fieldList );
		if ($size == 0) {
			return false;
		}
		$values = array ();
		for($i = 0; $i < $size; $i ++) {
			$field = &$fieldList [$i];
			$val = $rubikDBO->$field;
			if ($val === null) {
				$values [] = 'NULL';
			} else {
				$values [] = $this->dbQuoteData ( $val );
			}
			$field = $this->dbQuoteField ( $field );
		}
		$valuesStr = implode ( ", ", $values );
		$fieldsStr = implode ( ", ", $fieldList );
		
		$tblName = $this->dbQuoteField ( $this->_tbl_name );
		$sql = "INSERT INTO $tblName";
		$sql .= "\n (" . $fieldsStr . ")";
		$sql .= "\n VALUES (" . $valuesStr . ")";
		
		return $sql;
	}
	
	/**
	 * Prepare data and get the field list insite $rubikDBO
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 * @param boolean $returnDbField true return value is RubikDbField else return string
	 * @return array string
	 */
	function getFieldList($rubikDBO, $ignoreNull = true, $returnDbField = false) {
		if (! $rubikDBO->isPreparedData ()) {
			$this->prepareData ( $rubikDBO );
		}
		return $this->_allFields->getFieldList ( $rubikDBO, $ignoreNull, $returnDbField );
	}
	
	/**
	 * Get RubikDBField of key field
	 *
	 * @return RubikDBField
	 */
	function getKeyField() {
		return $this->_allFields->getDBField ( $this->getKeyName () );
	}
	
	/**
	 * Get the DB Field corresonding to the name
	 *
	 * @param string $fieldName
	 * @return RubikDBField
	 */
	function &getDbField($fieldName) {
		$allField = $this->_allFields;
		return $allField->getDBField ( $fieldName );
	}
	
	/**
	 * Create the delete SQL
	 *
	 * @param int/array $idOrArrayId
	 * @param AbstractRubikDBObject $rubikDBO
	 * @return string
	 */
	function createDeleteSQL($idOrArrayId, $rubikDBO = null) {
		if (empty ( $this->_tbl_name )) {
			return false;
		}
		
		$keyField = $this->getKeyField ();
		if (is_array ( $idOrArrayId )) {
			$arrId = array ();
			if ($rubikDBO) {
				foreach ( $idOrArrayId as $k => $id ) {
					$id = $keyField->prepareDataForDB ( $id );
					$arrId [$k] = $this->dbQuoteData ( $id );
				}
			} else {
				$arrId = $idOrArrayId;
			}
			$where = implode ( ", ", $arrId );
		} else {
			if ($rubikDBO) {
				$idOrArrayId = $this->dbQuoteData ( $idOrArrayId );
			}
			$where = $idOrArrayId;
		}
		$key = $keyField->getNameWithAlias ( $this );
		
		$where = "WHERE $key IN ($where)";
		
		if ($rubikDBO) {
			$tblName = $this->dbQuoteField ( $this->_tbl_name );
		} else {
			$tblName = $this->_tbl_name;
		}
		
		$sql = "DELETE FROM $tblName $where";
		return $sql;
	}
	
	////////////////////////////////////////////////////////////////////////////
	

	/**
	 * Add Slashes for SQL query
	 * OVERIDE
	 * 
	 * @param string $str
	 * @return string
	 */
	function dbAddSlashes($str) {
		if (get_magic_quotes_gpc ()) {
			return $str;
		}
		$db = $this->_db;
		if (empty ( $db )) {
			if (! is_string ( $str )) {
				return $str;
			}
			return addslashes ( $str );
		}
		$str = $db->getEscaped ( $str );
		return $str;
	}
	
	/**
	 * Add the quote for dbFiled
	 * OVERRIDE
	 *
	 * @param string $string
	 */
	function dbQuoteField($field) {
		$db = $this->_db;
		if (! $db || get_class ( $db ) === 'SiteException') {
			return $field;
		}
		$field = $db->nameQuote ( $field );
		return $field;
	}
	
	/**
	 * Convert date data from data into view
	 * OVERIDE
	 *
	 * @param string $data
	 * @param string $dateType date time type. ("D", "DT", "T", "TS")
	 */
	function convertDateDbToView($data, $dateType) {
		$db = $this->_db;
		if (empty ( $db ) || empty ( $data )) {
			return $data;
		}
		$toFormat = SystemSite::getFormatDateView ( $dateType );
		$fromFormat = $db->getFormatDateTime ( $dateType );
		
		RCoreImporter::cls( "helper.RDateTimeHelper" );
		if (RDateTimeHelper::isParsed ( $toFormat, $data )) {
			return $data;
		}
		
		return RDateTimeHelper::reFormat ( $toFormat, $fromFormat, $data );
	}
	
	/**
	 * Convert date data from data into view 
	 * OVERIDE
	 * 
	 * @param string $data
	 * @param string $dateType date time type. ("D", "DT", "T", "TS")
	 */
	function convertDateViewToDb($data, $dateType) {
		$db = $this->_db;
		if (empty ( $db ) || empty ( $data )) {
			return $data;
		}
		$fromFormat = SystemSite::getFormatDateView ( $dateType );
		$toFormat = $db->getFormatDateTime ( $dateType );
		
		RCoreImporter::cls( "helper.RDateTimeHelper" );
		if (RDateTimeHelper::isParsed ( $toFormat, $data )) {
			return $data;
		}
		return RDateTimeHelper::reFormat ( $toFormat, $fromFormat, $data );
	}
	
	/**
	 * Quote data if necessary
	 *
	 * @param string $value
	 * @param RubikDBField $dbField
	 */
	function dbQuoteData($value, $withAddslass = true) {
		if (is_string ( $value )) {
			if ($withAddslass) {
				$value = $this->dbAddSlashes ( $value );
			}
			return "'$value'";
		}
		return $value;
	}
	
	/**
	 * Get the database
	 *
	 * @return Database
	 */
	function &getDatabase() {
		return $this->_db;
	}
	
	/**
	 * Get the SELECT count(*) of this DBO
	 *
	 * @param string $where
	 * @return string ex: SELECT count(*) FROM user u JOIN user_group g ON u.group_id = g.group_id
	 */
	function getSelectCountSQL($where) {
		$sql = "SELECT count(*) ";
		$sql .= "\n" . $this->getFromClause ();
		$sql .= "\n $where";
		return $sql;
	}
	
	/**
	 * Increase the value of $increaseField inside this DTO up $increaseStep 
	 *
	 * @param AbstractRubikDTO $rubikDBO
	 * @param string $increaseField
	 * @param int $increaseStep
	 * @return int increased number
	 */
	function increaseVal($rubikDBO, $increaseField, $increaseStep = 1) {
		$db = $this->_db;
		$incField = $this->_allFields->getDBField ( $increaseField );
		$keyField = $this->getKeyField ();
		
		$increaseField = $incField->getNameWithAlias ( $this );
		
		$keyName = $keyField->fieldName;
		$keyVal = $rubikDBO->$keyName;
		$keyVal = $this->dbAddSlashes ( $keyVal );
		$keyName = $keyField->getNameWithAlias ( $this );
		$tblName = $this->dbQuoteField ( $this->getTableName () );
		
		$where = " WHERE $keyName=$keyVal";
		
		$query = "SELECT $increaseField FROM $tblName $where";
		$db->setQuery ( $query );
		$val = $db->loadResult ();
		
		$val += $increaseStep;
		$query = "UPDATE $tblName SET $increaseField=$val $where";
		$db->setQuery ( $query );
		$db->query ();
		$rubikDBO->setError ( $db->getErrors () );
		return $val;
	}
	
	/**
	 * Remove the slash when php enable mode: magic_quotes_gpc .
	 * Using this function before set data into view.
	 * 
	 * The idea of this: When a form submit then bind value into a DBO (magic_quotes_gpc = on)
	 * if you want to fill data back from current DBO into form --> you must use this function 
	 * for smart strip the slash.
	 * 
	 * Why smart?
	 * 	This function just strip the slash if (magic_quotes_gpc == on)
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 * @return boolean strip or not?
	 */
	function smartStripSlash(&$rubikDBO) {
		return $this->_allFields->smartStripSlash ( $rubikDBO );
	}
	
	/**
	 * Remove the slash when php enable mode: magic_quotes_gpc .
	 * Using this function before set data into view.
	 * 
	 * The idea of this: When a form submit then bind value into a DBO (magic_quotes_gpc = on)
	 * if you want to fill data back from current DBO into form --> you must use this function 
	 * for smart strip the slash.
	 * 
	 * Why smart?
	 * 	This function just strip the slash if (magic_quotes_gpc == on)
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 * @return boolean strip or not?
	 */
	function smartAddSlashes(&$rubikDBO) {
		return $this->_allFields->smartAddSlashes( $rubikDBO );
	}
	
	/**
	 * Trim the string of public properties in DTO
	 *
	 * @param AbstractRubikDBObject $dbo
	 * @param string $charlist
	 * 
	 * @return AbstractRubikDBObject
	 */
	function trimString(&$rubikDBO, $charlist = null) {
		$allFields = $this->_allFields;
		return $allFields->trimString ( $rubikDBO, $charlist );
	}
	
	/**
	 * Get all field of DTO
	 *
	 * @return array of Field
	 */
	function getAllField() {
		$allFields = $this->_allFields;
		return $allFields;
	}

}
?>
